动态代理
代理设计模式的原理
使用一个代理对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上。
动态代理的实现
ArithmeticCalculator.java
12345678package com.glemontree.spring.aop.helloworld;public interface ArithmeticCalculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);}ArithmeticCalculatorImpl.java
123456789101112131415161718192021222324package com.glemontree.spring.aop.helloworld;public class ArithmeticCalculatorImpl implements ArithmeticCalculator{public int add(int i, int j) {int result = i + j;return result;}public int sub(int i, int j) {int result = i - j;return result;}public int mul(int i, int j) {int result = i * j;return result;}public int div(int i, int j) {int result = i / j;return result;}}ArithmeticCalculatorImpl
是ArithmeticCalculator
的一个实现类,其并没有加上日志功能。
ArithmeticCalculatorLoggingProxy.java
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647package com.glemontree.spring.aop.helloworld;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Arrays;public class ArithmeticCalculatorLoggingProxy {private ArithmeticCalculator target;public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {this.target = target;}public ArithmeticCalculator getLoggingProxy() {ArithmeticCalculator proxy = null;// 代理对象由哪一个类加载器进行加载ClassLoader loader = target.getClass().getClassLoader();// 代理对象的类型,即其中有哪些方法Class[] interfaces = new Class[]{ArithmeticCalculator.class};// 当调用代理对象其中的方法时,该执行的代码InvocationHandler h = new InvocationHandler() {/*** proxy: 正在返回的代理对象,一般情况下在invoke方法中都不使用该对象* method:正在被调用的方法* args:调用方法时传入的参数**/public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {String methodName = method.getName();// 日志System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));// 执行方法Object result = method.invoke(target, args);// 日志System.out.println("The method " + methodName + " ends with " + result);return result;}};proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);return proxy;}}ArithmeticCalculatorLoggingProxy
是ArithmeticCalculatorImpl
的代理类,其含有一个ArithmeticCalculator
类型的对象,且需要在构造函数中进行初始化。ArithmeticCalculatorLoggingProxy
类中通过getLoggingProxy()
方法返回代理对象,代理对象的创建通过Proxy.newProxyInstance()
方法进行创建,代理对象的创建不同于普通对象的创建,普通对象一般都是通过new
进行创建,代理对象必须通过Proxy.newProxyInstance()
方法进行创建。Proxy.newProxyInstance()
方法需要三个参数,分别是:loader
interfaces
h
loader
顾名思义表示加载器,也就是代理对象由哪一个类加载器进行加载,这里loader
是这样创建的:1ClassLoader loader = target.getClass().getClassLoader();interfaces
表示代理对象的类型,即其中有哪些方法,interfaces
是这样创建的:1Class[] interfaces = new Class[]{ArithmeticCalculator.class};h
表示当调用代理对象其中的方法时,该执行的代码,这里是这样创建的:1234567891011121314151617181920InvocationHandler h = new InvocationHandler() {/*** proxy: 正在返回的代理对象,一般情况下在invoke方法中都不使用该对象* method:正在被调用的方法* args:调用方法时传入的参数**/public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {String methodName = method.getName();// 日志System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));// 执行方法Object result = method.invoke(target, args);// 日志System.out.println("The method " + methodName + " ends with " + result);return result;}};h
的类型为InvocationHandler
类型,在创建h
时需要实现其public Object invoke(Object proxy, Method method, Object[] args)
方法,该方法有三个参数:proxy
:正在返回的代理对象,一般情况下在invoke
方法中都不使用该对象method
:正在被调用的方法args
:调用方法时传入的参数
当使用代理对象调用方法时,会转而调用该方法,在该方法中可以加上自己需要的日志信息或其他信息,当然在该方法中还需要调用真正的需要调用的方法,这是通过下面这行代码实现的:
Object result = method.invoke(target, args);
,最后将result
返回即可。
Main.java
12345678910111213141516171819package com.glemontree.spring.aop.helloworld;public class Main {public static void main(String[] args) {/*ArithmeticCalculator arithmeticCalculator = null;arithmeticCalculator = new ArithmeticCalculatorLoggingImpl();int result = arithmeticCalculator.add(1, 2);System.out.println("-->" + result);result = arithmeticCalculator.div(4, 2);System.out.println("-->" + result);*/ArithmeticCalculator target = new ArithmeticCalculatorImpl();ArithmeticCalculator proxy = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy();int result = proxy.add(1, 2);System.out.println("-->" + result);result = proxy.div(4, 2);System.out.println("-->" + result);}}
AOP简介
AOP(Aspect-Oriented Programming,面向切面编程)是一种新的方法论,是对传统OOP的补充!
AOP的好处:
- 每个事物逻辑位于一个位置,代码不分散,便于维护和升级
- 业务模块更简洁,只包含核心业务代码
AOP图解
- 切面:横切关注点(跨越应用程序多个模块的功能),被模块化的特殊对象
- 通知:切面必须要完成的工作
- 目标:被通知的对象
- 代理:向目标对象应用通知之后创建的对象
- 连接点:程序执行的某个特定位置,如类中某个方法调用前、调用后、方法抛出异常后等,连接点由两个信息确定,方法表示的程序执行点,相对点表示的方位
- 切点:每个类都拥有多个连接点,例如
ArithmethicCalculator
的所有方法都是连接点,即连接点是程序类中客观存在的事务,AOP通过切点定位到特定的连接点,类比:连接点相当于数据库中的记录,切点相当于查询条件,切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。
Spring AOP前置通知
AspectJ:java社区里最完整最流行的AOP框架
可以使用基于AspectJ注解或基于XML配置的AOP
使用Spring AOP
加入jar包:
- com.springsource.net.sf.cglib-2.2.0.jar
- com.springsource.org.aopalliance-1.0.0.jar
- com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
- commons-logging-1.1.1.jar
- spring-aop-4.0.0.RELEASE.jar
- spring-aspects-4.0.0.RELEASE.jar
- spring-beans-4.0.0.RELEASE.jar
- spring-context-4.0.0.RELEASE.jar
- spring-core-4.0.0.RELEASE.jar
- spring-expression-4.0.0.RELEASE.jar
在配置文件中加入aop的命名空间:
xmlns:aop=”http://www.springframework.org/schema/aop“
基于注解的方式:
在配置文件中加入如下配置:
1<aop:aspectj-autoproxy></aop:aspectj-autoproxy>这个注解的作用是使Aspect注解起作用:自动为匹配的类生成代理对象
把横切关注点的代码抽象到切面的类中
切面的类首先是一个IOC容器中bean,并加入@Component注解
切面还需要加入@Aspect注解
在类中声明各种通知
常见的有5种通知,分别是:
@Before:前置通知,在方法执行之前执行
- @After:后置通知,在方法执行之后执行
- @AfterRunning:返回通知,在方法返回结果之后执行
- @AfterThrowing:异常通知,在方法抛出异常之后
@Around:环绕通知,围绕着方法执行
声明通知其实就是声明方法:
声明一个方法
- 在方法前加入@Before注解
- 可以在声明方法中声明一个类型为JointPoint类型的参数,然后就能访问链接细节,如方法名称和参数值
1234567891011121314151617181920212223package com.glemontree.spring.aop.impl;import java.util.ArrayList;import java.util.Arrays;import java.util.List;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.stereotype.Component;// 把这个类声明为一个切面:需要把该类放入到IOC容器中,再声明为一个切面public class LoggingAspect {//声明该方法为一个前置通知:在目标方法开始执行之前执行"execution(* com.glemontree.spring.aop.impl.*.*(int, int))")(public void beforeMethod(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();List<Object> args = Arrays.asList(joinPoint.getArgs());System.out.println("The method " + methodName + " begins with " + args);}}
后置通知
和前置通知很类似,后置通知使用@After注解:
|
|
需要注意的是后置通知是在目标方法执行之后执行,其不管是否会发生异常都会执行。另外需要注意后置通知中不能访问目标方法执行的结果。
返回通知
|
|
异常通知
|
|
环绕通知
环绕通知类似于动态代理,虽然功能很强大,但是用的不多,这里不加以介绍!
切面的优先级
可以通过@Order注解指定切面的优先级,值越小优先级越高。
|
|
重用切点表达式
|
|
对于同一个包下的另一个切面,可以使用下面的方式:
|
|
当然,对于不同包下的切面,可能还需要使用包名。